#include <atlbase.h>
#include <sapi.h>

#include <string>
#include <functional>
#include <memory>
#include <iostream>
#include <fstream>

#include "detours.h"
#pragma comment (lib, "detours.lib")

#include "nrapi"
#pragma comment (lib, "nrcore.lib")

#include "SpEngine.h"
#include "resource.h"

using namespace std;

static wofstream gLog;
static long gNotepadWndProc;
static long gEditWndProc;
static bool gInited = false;
CComPtr<ISpVoice> spVoice;


SpEngine::SpEngine(void) : _disposed(false)
{
}


SpEngine::~SpEngine(void)
{
	if(!_disposed)
		Cleanup();
}

void ProceedKey(wchar_t* text, WORD vKey)
{
	if(vKey == VK_TAB)
		wcscpy(text, L"tab");
	else if(vKey == VK_DELETE)
		wcscpy(text, L"delete");
	else if(vKey == VK_BACK)
		wcscpy(text, L"backspace");
	else if(vKey == VK_RETURN)
		wcscpy(text, L"enter");
	else if(vKey >= 0x30 && vKey <= 0x39)
	{ text[0] = vKey - 0x30 + '0'; text[1] = L'\0'; }
	else if(vKey >= 0x41 && vKey <= 0x5A)
	{ text[0] = vKey - 0x41 + 'A'; text[1] = L'\0'; }
	else if(vKey >= VK_F1 && vKey <= VK_F24)
	{ text[0] = 'F'; wsprintfW(text + 1, L"%d", vKey - VK_F1 + 1); }

}

//------------------------------------------------------------------------
// EditWndProc
LONG WINAPI EditWndProc(
	__in HWND hWnd,
	__in UINT Msg,
	__in WPARAM wParam,
	__in LPARAM lParam)
{
	switch(Msg)
	{
	case WM_KEYDOWN:
		if(wParam == VK_F11)
		{
			int nLength = GetWindowTextLengthW(hWnd) + sizeof(wchar_t);
			wchar_t* buffer = new wchar_t[nLength];
			GetWindowTextW(hWnd, buffer, nLength);
			spVoice->Speak(buffer, 1, NULL);
			delete[] buffer;
		}
		else
		{
			wchar_t text[16] = {0};
			ProceedKey(text, wParam);
			if(text && *text)
			{
				gLog << L"say: " << text << endl;
				spVoice->Speak(text, 1, NULL);
			}
		}
		break;
	}

	return CallWindowProcW((WNDPROC)gEditWndProc, hWnd, Msg, wParam, lParam);
}
//------------------------------------------------------------------------


//------------------------------------------------------------------------
// NotepadWndProc
LONG WINAPI NotepadWndProc(
	__in HWND hWnd,
	__in UINT Msg,
	__in WPARAM wParam,
	__in LPARAM lParam)
{
	switch(Msg)
	{
	case WM_SETTEXT:
		{
			gLog << L"WM_SETTEXT->" << (LPCWSTR)lParam << endl;
			lParam = (LPARAM)L"Untitled - SpeechPad";

			if(!spVoice)
			{
				// Create SpVoice
				CoInitialize(NULL);
				HRESULT hr = spVoice.CoCreateInstance(L"SAPI.SpVoice");
				if(FAILED(hr))
					MessageBox::Show(L"Error CoCreateInstance('SAPI.SpVoice')");

				spVoice->Speak(L"Welcome to use SpeechPad!", 1, NULL);
			}

			if(!gInited)
			{
				HWND hEdit = GetDlgItem(hWnd, 15);
				gLog << L"hEdit->" << hEdit << endl;
				gEditWndProc = SetWindowLongPtrW(hEdit, GWLP_WNDPROC, (LONG)EditWndProc);

				gInited = true;
			}
		}
		break;
	}

	return CallWindowProcW((WNDPROC)gNotepadWndProc, hWnd, Msg, wParam, lParam);
}
//------------------------------------------------------------------------


//------------------------------------------------------------------------
// hook RegisterClassW@user32.dll
static ATOM (WINAPI* RegisterClassExNext)(
	__in CONST WNDCLASSEXW *lpWndClass) = RegisterClassExW;

ATOM
WINAPI
RegisterClassExCallback(
	__in CONST WNDCLASSEXW *lpWndClass) 
{
	gLog << L"RegisterClassEx->" << lpWndClass->lpszClassName << endl;

	// Replace lpWndClass->lpfnWndProc
	if(wcscmp(lpWndClass->lpszClassName, L"Notepad") == 0)
	{
		WNDCLASSEXW wnd;
		memcpy(&wnd, lpWndClass, sizeof(WNDCLASSEXW));
		gNotepadWndProc = (long)wnd.lpfnWndProc;
		wnd.lpfnWndProc = NotepadWndProc;	
		return RegisterClassExNext(&wnd);
	}
	else
		return RegisterClassExNext(lpWndClass);
}
//------------------------------------------------------------------------



//------------------------------------------------------------------------
// hook RegisterClassW@user32.dll
//static ATOM (WINAPI* RegisterClassNext)(
//	__in CONST WNDCLASSW *lpWndClass) = RegisterClassW;
//
//ATOM
//WINAPI
//RegisterClassCallback(
//	__in CONST WNDCLASSW *lpWndClass) 
//{
//	gLog << L"RegisterClass->" << lpWndClass->lpszClassName << endl;
//	if(wcscmp(lpWndClass->lpszClassName, L"Notepad") == 0)
//	{
//		WNDCLASSW wnd;
//		memcpy(&wnd, lpWndClass, sizeof(WNDCLASSW));
//		gWndProc = (long)wnd.lpfnWndProc;
//		wnd.lpfnWndProc = NotepadWndProc;	
//		return RegisterClassNext(&wnd);
//	}
//	else
//		return RegisterClassNext(lpWndClass);
//}
//------------------------------------------------------------------------



void SpEngine::Startup(void)
{
	gLog.open(L"log.txt");

	// Detour RegisterClass invokes which register a 'Notepad' class name
	DetourTransactionBegin();
	DetourUpdateThread(GetCurrentThread());
	//DetourAttach((PVOID*)&RegisterClassNext, RegisterClassCallback);
	DetourAttach((PVOID*)&RegisterClassExNext, RegisterClassExCallback);
	DetourTransactionCommit();

}


void SpEngine::Cleanup(void)
{
	if(_disposed)
		return;

	DetourTransactionBegin();
	DetourUpdateThread(GetCurrentThread());
	//DetourDetach((PVOID*)&RegisterClassNext, RegisterClassCallback);
	DetourDetach((PVOID*)&RegisterClassExNext, RegisterClassExCallback);
	DetourTransactionCommit();

	// Cleanup SpVoice

	if(gLog.is_open())
	{
		gLog << L"Log closed" << endl;
		gLog.close();
	}

	if(spVoice)
	{
		spVoice.Release();
		spVoice = NULL;
	}
	CoUninitialize();

	//
	_disposed = true;
}
